#include "CollisionUtil.h"

//-------------------------------------------------------------------------------------------------------
CollisionUtil::CollisionUtil()
{ }

//-------------------------------------------------------------------------------------------------------
CollisionUtil::~CollisionUtil()
{ }

//-------------------------------------------------------------------------------------------------------
void CollisionUtil::CollisionUpdateCamera(
	ID3D11DeviceContext*	dc,							// Pointer to the application device context.
	bool					dollying,					// Flag to signify if the camera is dollying.
	vector<InstanceMap*>&	instanceMaps,				// Application vector of instance maps.
	vector<ModelData*>&		modelData,					// Application vector of model data.
	Camera*					pCamera,					// Pointer to camera.
	float					desiredDistance,			// Desired distance to dolly camera.
	float					dt)							// Application delta-time.
{
	bool bCollision = false;

	if (pCamera->IsThirdPerson())		// If camera is in third-person mode.
	{
		if (dollying)					// If we are dollying, check for dolly collisions.
		{
			for (UINT i = 0; i < instanceMaps.size(); ++i)
			{
				if (bCollision) break;
			
				bCollision = CollisionDollyOut(dc, instanceMaps[i], modelData[i], pCamera);
			}

			if (!bCollision) pCamera->UpdateDistance(desiredDistance);
		}
		else							// Otherwise, check for camera collisions.
		{
			for (UINT i = 0; i < instanceMaps.size(); ++i)
			{
				CollisionCheckCamera(dc, instanceMaps[i], modelData[i], pCamera, dt);
			}
		}
	}
}

//-------------------------------------------------------------------------------------------------------
void CollisionUtil::CollisionCheckCamera(				// Static method that checks rotational camera collision.
	ID3D11DeviceContext*	deviceContext,				// Pointer to the application device context.
	InstanceMap*			pInstanceMap,				// Pointer to the instance map to access instance data.
	ModelData*				pModelData,					// Pointer to the instance model data to retrieve AABB.
	Camera*					pCamera,					// Pointer to camera.
	float					dt)							// Frames delta-time.
{

	D3D11_MAPPED_SUBRESOURCE mappedData;				// Subresource variable to store mapped data pointer.

	deviceContext->Map(									// Map the InstanceMap dynamic vertex buffer.
		pInstanceMap->GetInstancedBuffer(), 0,			// Retrieve dynamic vertex buffer pointer from InstanceMap.
		D3D11_MAP_WRITE_NO_OVERWRITE, 0,				// Specify no overwrite flag as we only want to read the data.
		&mappedData);

	// Create a pointer to access the mapped instanced data.
	InstancedData* dataView = reinterpret_cast<InstancedData*>(mappedData.pData);

	// Loop through the InstanceMap's visible instances for detecting collisions with the camera.
	for (UINT i=0; i < pInstanceMap->GetNumVisible(); ++i)
	{
		// Create a projection matrix for the temporary collision frustum.
		XMMATRIX Proj = XMMatrixPerspectiveFovLH(
			0.0001f*XM_PI,								// Fractional FOV (field-of-view) to generate small far plane.
			1.0f,										// Square aspect ratio.
			1.0f,										// Near plane value.
			pCamera->GetDistance());					// Far plane distance is the same distance from the camera to character.

		// Create a frustum using the generated projection matrix.
		XNA::Frustum collisionFrustum;
		XNA::ComputeFrustumFromProjection(&collisionFrustum, &Proj);

		// Calculate camera's inverse view matrix to transform it into world space.
		XMMATRIX invView = XMMatrixInverse(&XMMatrixDeterminant(pCamera->View()), pCamera->View());
		
		// Decompose the camera's inverse view matrix.
		XMVECTOR scale, rotQuat, translation;
		XMMatrixDecompose(&scale, &rotQuat, &translation, invView);

		// Transform the camera from view space to world space.		
		XNA::TransformFrustum(&collisionFrustum, &collisionFrustum, XMVectorGetX(scale), rotQuat, translation);
		
		// Get a copy of the instance bounding box.
		XNA::AxisAlignedBox instAABB = *pModelData->GetAABBp();
		
		// Now transform the bounding box from local space to world space using the instance's world matrix.
		instAABB.Center.x = dataView[i].World._41;
		instAABB.Center.y = dataView[i].World._42;
		instAABB.Center.z = dataView[i].World._43;

		// Perform the box/frustum intersection test in world space.
		if (XNA::IntersectAxisAlignedBoxFrustum(&instAABB, &collisionFrustum) != 0)
		{
			// If there is a collision, firstly check that the camera is more than 5 units away from the character.
			if (pCamera->GetDistance() > 5.0f)
			{
				// Dolly the camera towards the character.
				pCamera->UpdateDistance(dt * (pCamera->GetDistance()  * -5.0f));
			}
		}
	}
	
	// Unmap the InstanceMap dynamic vertex buffer.
	deviceContext->Unmap(pInstanceMap->GetInstancedBuffer(), 0);
}

//-------------------------------------------------------------------------------------------------------
bool CollisionUtil::CollisionDollyOut(					// Static method that checks dolly-out camera collision.
	ID3D11DeviceContext*	deviceContext,				// Pointer to the application device context.
	InstanceMap*			pInstanceMap,				// Pointer to the instance map to access instance data.
	ModelData*				pModelData,					// Pointer to the instance model data to retrieve AABB.
	Camera*					pCamera)					// Pointer to camera.
{
	D3D11_MAPPED_SUBRESOURCE mappedData;				// Subresource variable to store mapped data pointer.

	deviceContext->Map(									// Map the InstanceMap dynamic vertex buffer.
		pInstanceMap->GetInstancedBuffer(), 0,			// Retrieve dynamic vertex buffer pointer from InstanceMap.
		D3D11_MAP_WRITE_NO_OVERWRITE, 0,				// Specify no overwrite flag as we only want to read the data.
		&mappedData);

	// Create a pointer to access the mapped instanced data.
	InstancedData* dataView = reinterpret_cast<InstancedData*>(mappedData.pData);

	// Loop through the InstanceMap's visible instances for detecting collisions with the camera.
	for (UINT i=0; i < pInstanceMap->GetNumVisible(); ++i)
	{
		// Create a projection matrix for the temporary collision frustum.
		XMMATRIX Proj = XMMatrixPerspectiveFovLH(
			0.0001f*XM_PI,								// Fractional FOV (field-of-view) to generate small far plane.
			1.0f, 										// Square aspect ratio.
			1.0f, 										// Near plane value.
			pCamera->GetDistance());					// Far plane distance is the same distance from the camera to character.

		// Create a frustum using the generated projection matrix.
		XNA::Frustum collisionFrustum;
		XNA::ComputeFrustumFromProjection(&collisionFrustum, &Proj);

		// Calculate camera's inverse view matrix to transform it into world space.
		XMMATRIX invView = XMMatrixInverse(&XMMatrixDeterminant(pCamera->View()), pCamera->View());
	
		// Decompose the camera inverse view matrix.
		XMVECTOR scale, rotQuat, translation;
		XMMatrixDecompose(&scale, &rotQuat, &translation, invView);

		// Transform the camera from view space to world space.		
		XNA::TransformFrustum(&collisionFrustum, &collisionFrustum, XMVectorGetX(scale), rotQuat, translation);
		
		// Get a copy of the instance bounding box.
		XNA::AxisAlignedBox instAABB = *pModelData->GetAABBp();
		
		// Now transform the bounding box from local space to world space using the instance's world matrix.
		instAABB.Center.x = dataView[i].World._41;
		instAABB.Center.y = dataView[i].World._42;
		instAABB.Center.z = dataView[i].World._43;
		
		// Perform the box/frustum intersection test in world space.
		if (XNA::IntersectAxisAlignedBoxFrustum(&instAABB, &collisionFrustum) != 0)
		{
			// Unmap the InstanceMap dynamic vertex buffer.
			deviceContext->Unmap(pInstanceMap->GetInstancedBuffer(), 0);
			
			return true;
		}
	}
	
	// Unmap the InstanceMap dynamic vertex buffer.
	deviceContext->Unmap(pInstanceMap->GetInstancedBuffer(), 0);

	return false;
}

//-------------------------------------------------------------------------------------------------------
void CollisionUtil::UpdateTitleBarStats(std::wstring& caption, vector<InstanceMap*>& instanceMaps)
{
	// Update application title bar to display the number of visible objects.

	wostringstream outs;			// Create a wide output string stream.
	outs.precision(6);				// Floating-point values will display 6 decimal places.

	UINT visibleObjects = 0;
	UINT totalObjects   = 0;

	// Calculate the number of visible objects the camera can see, and the total number 
	// of objects in the level.
	for (UINT i = 0; i < instanceMaps.size(); ++i)
	{
		visibleObjects += instanceMaps[i]->GetNumVisible();
		totalObjects   += instanceMaps[i]->GetInstancedDataP()->size();
	}

	outs << "Character Controller Demo"  << L"    " << visibleObjects <<
		    L" objects visible out of "  << totalObjects;

	caption = outs.str();			// Update windows title bar caption.
}